Data Catalog のポリシータグで BigQuery カラムレベルのアクセス制御が可能になったので試してみた
こんにちは、みかみです。
これまで BigQuery のデータのアクセス制御を指定できる最下層のリソースはデータセットで、テーブルやカラム単位でのアクセス制御はできませんでしたが、Data Catalog のポリシータグを付与することで、カラムレベルのアクセス制御が指定できるようになったそうです。
- BigQuery の列レベルのセキュリティで、きめ細かなアクセス制御を | GCP ブログ
- Introduction to BigQuery Column-level security | BigQuery ドキュメント
- Restricting access with BigQuery Column-level security | BigQuery ドキュメント
やりたいこと
- BigQuery のカラムレベルのアクセス制御を行うにはどうすればいいのか知りたい
- BigQuery のカラムレベルのアクセス制御を設定した場合の挙動を確認したい
前提
2020/04/07 現在、この Data Catalog のポリシータグを使用した BigQuery カラムレベルのアクセス制御機能はベータ版とのことです。
動作確認するにあたって、GCP で操作対象のプロジェクトに対して課金が有効になっていること、Data Catalog のポリシータグを作成する権限があることを前提としています。
また、Python クライアントライブラリを使用した BigQuery へのアクセス環境は準備済みであるものとします。
Data Catalog のポリシータグを作成
GCP 管理コンソールナビゲーションメニューから Data Catalog を選択し、APIを有効にします。
Data Catalog 画面のポリシータグ項目の「ポリシータグを作成、管理する」リンクをクリックし、ポリシータグ一覧画面で「作成」をクリック。新規ポリシータグを作成します。
「分類名」と「説明」に任意の文字列を入力し、「プロジェクト」と「ロケーション」を選択、「ポリシータグ」に任意のタグ名と説明を入力して「保存」します。
「High」と「Medium」2つのポリシータグを作成しました。
ポリシータグ詳細画面で「アクセス制御の適用」を ON に設定します。
さらに、作成したポリシータグに「メンバーを追加」します。
「新しいメンバー」欄に追加するメンバーアカウントを入力し、「ロール」プルダウンで「データカタログ」の「きめ細かい読み取り」を設定します。
「High」のポリシータグにはサービスアカウントAの参照権限を追加し、
「Medium」のポリシータグにはサービスアカウントAとサービスアカウントBの参照権限を追加しました。
BigQuery のテーブル項目にポリシータグを設定
BigQuery 管理画面から、テーブルを選択し、「スキーマを編集」します。
アクセス制御したいカラムにチェックを入れて、「ポリシータグを追加」ボタンをクリック。
ポリシータグ追加画面で付与するポリシーを「選択」。
高レベルセキュリティの「High」と中レベルセキュリティの「Medium」2つのポリシータグを、それぞれ別のカラムに設定しました。
なお、現在のところ、DDL( CREATE TABLE 文)によるテーブル作成時には、ポリシータグは設定できないとのことです。
ポリシータグ設定済みのテーブルデータを参照してみる
現在、以下の状況です。
- 高レベルセキュリティの「High」と中レベルセキュリティの「Medium」の2つのポリシータグあり
- テーブル name_1880 の name カラムには「High」ポリシー設定済み
- テーブル name_1880 の gender カラムには「Medium」ポリシー設定済み
- テーブル name_1880 の count カラムにはポリシー未設定
- サービスアカウントAは「High」「Medium」ポリシーどちらのカラムも参照可能
- サービスアカウントBは「High」ポリシーは参照不可。「Medium」ポリシーは参照可能
アカウントキーファイルを指定して、高レベル、中レベル両方のカラムを SELECT する Python コードを準備しました。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='select data') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) query = ( 'SELECT name, gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` ' 'ORDER BY count DESC LIMIT 3' ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) query_job = client.query(query) rows = query_job.result() for row in rows: print('{} : {} ({})'.format(row.name, row.gender, row.count))
高レベル、中レベルどちらのカラムも参照できるサービスアカウントAのアカウントキーで実行してみると
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_1.py key_account_a.json John : M (9655) William : M (9532) Mary : F (7065)
両方のカラム値を正常に取得できます。
高レベルのカラムは参照できないサービスアカウントBのアカウントキーに変更して実行してみると
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_1.py key_account_b.json Traceback (most recent call last): File "test_select_level_1.py", line 26, in <module> rows = query_job.result() File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3196, in result super(QueryJob, self).result(retry=retry, timeout=timeout) File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 818, in result return super(_AsyncJob, self).result(timeout=timeout) File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 122, in result self._blocking_poll(timeout=timeout) File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3098, in _blocking_poll super(QueryJob, self)._blocking_poll(timeout=timeout) File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 101, in _blocking_poll retry_(self._done_or_raise)() File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 80, in _done_or_raise if not self.done(): File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3085, in done timeout=timeout, File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/client.py", line 1287, in _get_query_results retry, method="GET", path=path, query_params=extra_params, timeout=timeout File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/client.py", line 556, in _call_api return call() File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 GET https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/queries/01610338-5680-4f09-8564-36c26823ea7a?maxResults=0&location=US: Access Denied: BigQuery BigQuery: User does not have permission to access policy tag "分類テスト : High" on column cm-da-mikami-yuki-258308.airflow_test.name_1880.name. (job ID: 01610338-5680-4f09-8564-36c26823ea7a) -----Query Job SQL Follows----- | . | . | . | . | . | . | . | . | . | . | 1:SELECT name, gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` ORDER BY count DESC LIMIT 3 | . | . | . | . | . | . | . | . | . | . |
設定どおり、参照を許可していないサービスアカウントBでは、高レベルポリシーを指定したカラム値が permission エラーで取得できません。
続いて、中レベルのカラムを SELECT する 以下の Python コードを、アカウントA、Bそれぞれのアカウントキーを指定して実行してみます。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='select data') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) query = ( 'SELECT gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` ' 'ORDER BY count DESC LIMIT 3' ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) query_job = client.query(query) rows = query_job.result() for row in rows: print('{} ({})'.format(row.gender, row.count))
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_2.py key_account_a.json M (9655) M (9532) F (7065)
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_2.py key_account_b.json M (9655) M (9532) F (7065)
設定したポリシーの通り、A、Bどちらのアカウントでもカラムデータにアクセスできることが確認できました。
制限事項
現在のところ、東京リージョンの BigQuery データに対しては、Data Catalog のポリシータグによるカラムレベルのアクセス制御は指定できないようです。
管理コンソールからの Data Catalog のポリシータグ作成時、ロケーションには 「US」と「EU」のどちらかしか選択できません。
また、BigQuery テーブルのスキーマ編集画面でも、US リージョンのテーブルの場合はカラム選択のチェックボックスと「ポリシータグを追加」ボタンが表示されますが、
東京リージョンの場合、「ポリシータグを追加」ボタンが表示されません。
ポリシータグはチェックボックスや追加ボタンを使った UI 操作での付与の他に、テキストモードでのスキーマ指定でも付与可能とのことなので、東京リージョンであらかじめポリシータグを付与したテーブルを新規作成しようとしてみましたが
ポリシータグとデータセットが同一リージョンではないため、エラーになり、テーブル作成できませんでした。
まだベータ版とのことなので、東京リージョンのサポートを楽しみに待つことにいたします!
まとめ(所感)
管理コンソールからの UI 操作だけで、簡単に BigQuery データへのアクセス制御を設定することができました。今のところまだテーブルレベルでのアクセス制御機能はないようですが、このカラムレベルのポリシー指定により、テーブル単位のアクセス制御も柔軟に対応できますね。
他のデータベースサービスと比べると、DB 接続ユーザーがなかったり GRANT 構文によるアクセス権限設定機能がなかったり、初めはちょっと戸惑いましたが、アカウントの切り替えや、ロール、ポリシーの設定で同等のアクセス制御は実現できることが分かりました。
GCP を勉強し始めてからまだ日は浅いですが、サービスのアップデートのスピードも速く、BigQuery もどんどん使いやすくなっているように思います。 今後のさらなるアップデートにも期待です!